package com.personal.dataconvert;

import java.io.ByteArrayOutputStream;
import java.util.*;

import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;

import com.personal.core.data.DataRow;
import com.personal.core.data.DataTable;
import com.personal.core.data.DataTables;
import com.personal.core.utils.Assert;
import com.personal.core.utils.CoreUtil;
import com.personal.dataconvert.bean.CombineColumnConfig;
import com.personal.dataconvert.bean.HeaderConfig;
import com.personal.dataconvert.bean.SheetConfig;
import com.personal.dataconvert.bean.WorkBookConfig;
import com.personal.dataconvert.port.Data2Excel;
import com.personal.dataconvert.util.ExcelHtmlUtil;

/**
 * DataSet转Excel下载 说明： 规则报表：根据博微的DataSet格式。必须填充DataTable的DataColumn
 * 多表头仍采用博微的DataSet格式，使用 "." 分割 tag ：可以设置居左居右的样式 dataType : 中可以设置宽度 DataRow 中可以加
 * TRSTYLE 设置行样式 DataRow 中可以加 columnName + TDSTYLE 设置单元格样式 DataRow 中可以加TRCLICK
 * 设置点击事件 DataRow 中可以加 columnName + TDPOSTIL 设置单元格批注 左右页眉放在DataTable 中的 tag
 * 中，格式：LEFT:xxx,RIGHT:xxx
 * @author cuibo
 */
public class DataSet2Excel implements Data2Excel
{
    // DataSet数据集
    private DataTables ds;
    // Excel类型
    private String excelType;
    // 是否冻结表头
    private boolean freezeHeader = true;
    // workBook
    private Workbook workbook;
    /** 重新计算宽度（html宽度放大至Excel） */
    private boolean recalWidth = false;
    /** 冻结列数 */
    private int freezeColIndex = -1;
    /** 是否创建表头和标题 */
    private boolean createHeader = true;
    private boolean createTitle = true;
    /** splitFlag 表头分割标记。默认是点 */
    private String splitFlag = ".";
    /** 表头分隔符 */
    private Map<String, String> splitFlags;
    /** 2017 06 02 新增手动注入bookConfig */
    private WorkBookConfig bookConfig;
    /** 处理同名问题 */
    private Map<String, String> sameNameCache;
    /** 合并配置 */
    private Map<String, Set<? extends CombineColumnConfig>> combineMap;
    /** 页脚 */
    private Map<String, String> pagefotters;
    /** 标题 */
    private Map<String, String> titles;
    /** 合并配置 */
    private Map<String, List<CellRangeAddress>> rangeAddresses;
    /** 数据类型 */
    private Map<String, Map<String, String>> dataTypeMap;

    private DataSet2Excel()
    {
    }

    private DataSet2Excel(Builder builder)
    {
        ds = builder.ds;
        excelType = builder.excelType;
        freezeHeader = builder.freezeHeader;
        createHeader = builder.createHeader;
        createTitle = builder.createTitle;
        freezeColIndex = builder.freezeColIndex;
        workbook = builder.workbook;
        splitFlag = builder.splitFlag;
        recalWidth = builder.recalWidth;
        bookConfig = builder.bookConfig;
        combineMap = builder.combineMap;
        sameNameCache = builder.sameNameCache;
        pagefotters = builder.pagefotters;
        splitFlags = builder.splitFlags;
        rangeAddresses = builder.rangeAddresses;
        titles = builder.titles;
        dataTypeMap = builder.dataTypeMap;
    }

    /**
     * DataSet转Excel
     * @param ds
     * @return
     * @throws Exception
     */
    @Override
    public byte[] exportExcel() throws Exception
    {
        workbook = fillExcel();
        if (workbook == null)
        {
            return new byte[0];
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        workbook.write(out);
        return out.toByteArray();
    }

    /**
     * DataSet转Excel
     * @param ds
     * @return
     * @throws Exception
     */
    @Override
    public Workbook fillExcel() throws Exception
    {
        if (ds != null && !ds.isEmpty())
        {
            // 构建WorkBookConfig
            WorkBookConfig bookConfig = createWorkBookConfig();
            Map<String, List<?>> allObjectData = loadAllObjectData();
            Assert.isNotNull(bookConfig, "构架WorkBookConfig失败！");
            Data2Excel data2Excel = new ExportData2Excel.Builder().setExcelType(excelType).setWorkbook(workbook)
                    .setWorkBookConfig(bookConfig).setAllObjectData(allObjectData).build();
            workbook = data2Excel.fillExcel();
        }
        return workbook;
    }

    private SheetConfig createSheetConfig(WorkBookConfig result, DataTable table) throws Exception
    {
        List<HeaderConfig> headerConfigs;
        SheetConfig sheetConfig = result.newSheetConfig(table.getTableName());
        if (ExcelHtmlUtil.isIrregular(table))
        {
            sheetConfig.setIrregular(true);
            sheetConfig.setFreezeHeader(false);
        } else
        {
            // 表头表列相关信息
            if (createTitle)
            {
                sheetConfig.setTitle(
                        titles != null && titles.containsKey(table.getTableName()) ? titles.get(table.getTableName())
                                : table.getTableName());
            }
            sheetConfig.setFreezeColIndex(freezeColIndex);
            sheetConfig.setCreateHeader(createHeader);
            sheetConfig.setFreezeHeader(freezeHeader);
            sheetConfig.setCombineConfigs(combineMap == null ? null : combineMap.get(table.getTableName()));
            sheetConfig.setPagefotter(pagefotters == null ? null : pagefotters.get(table.getTableName()));
            sheetConfig.setRangeAddresses(rangeAddresses == null ? null : rangeAddresses.get(table.getTableName()));
            Map<String, String> dataType = dataTypeMap == null ? null : dataTypeMap.get(table.getTableName());
            if (splitFlags != null && splitFlags.containsKey(table.getTableName()))
            {
                headerConfigs = ExcelHtmlUtil.createTrees(table, splitFlags.get(table.getTableName()), dataType);
            } else
            {
                headerConfigs = ExcelHtmlUtil.createTrees(table, splitFlag, dataType);
            }
            Assert.isNotNullOrEmpty(headerConfigs, "构建" + table.getTableName() + "的表头节点失败！");
            // 设置SheetConfig并重新计算宽度,提供同名替换功能
            ExcelHtmlUtil.setSheetConfig(sheetConfig, headerConfigs, recalWidth, sameNameCache);
            sheetConfig.setLeftHeader(table.getLeftHeader());
            sheetConfig.setRightHeader(table.getRightHeader());
            sheetConfig.setTitle(table.getTitle());
            sheetConfig.setHeaderConfigs(headerConfigs);
        }
        return sheetConfig;
    }

    /**
     * 创建ds的WorkBookConfig
     * @return
     * @throws Exception
     */
    private WorkBookConfig createWorkBookConfig() throws Exception
    {
        if (bookConfig != null)
        {
            return bookConfig;
        }
        WorkBookConfig result = new WorkBookConfig();
        for (DataTable table : ds)
        {
            SheetConfig sheetConfig = createSheetConfig(result, table);
            result.getSheetConfigs().add(sheetConfig);
        }
        return result;
    }

    private Map<String, List<?>> loadAllObjectData()
    {
        Map<String, List<?>> result = new HashMap<String, List<?>>();
        List<Object> singleList = null;
        for (DataTable table : ds)
        {
            singleList = new ArrayList<Object>();
            if (table.getRows() == null || table.getRows().isEmpty())
            {
                continue;
            }
            for (DataRow object : table.getRows())
            {
                singleList.add(object.getItemMap());
            }
            result.put(table.getTableName(), singleList);
        }
        return result;
    }

    /**
     * 建造
     * @author cuibo
     */
    public static class Builder
    {
        private DataTables ds;
        private String excelType;
        private boolean freezeHeader = true;
        private boolean createHeader = true;
        private int freezeColIndex = -1;
        private Workbook workbook;
        private String splitFlag = ".";
        private Map<String, String> splitFlags;
        private boolean recalWidth = false;
        private WorkBookConfig bookConfig;
        private Map<String, String> sameNameCache;
        private Map<String, Set<? extends CombineColumnConfig>> combineMap;
        private Map<String, String> pagefotters;
        private Map<String, List<CellRangeAddress>> rangeAddresses;
        private boolean createTitle = true;
        private Map<String, String> titles;
        private Map<String, Map<String, String>> dataTypeMap;

        public Builder addCombine(String tableName, Set<? extends CombineColumnConfig> combine)
        {
            if (combineMap == null)
            {
                combineMap = new HashMap<String, Set<? extends CombineColumnConfig>>();
            }
            combineMap.put(tableName, combine);
            return this;
        }

        public Builder addData(DataTable table)
        {
            if (ds == null)
            {
                ds = new DataTables();
            }
            ds.add(table);
            return this;
        }

        public Builder addPagefotter(String tableName, String pagefotter)
        {
            if (pagefotters == null)
            {
                pagefotters = new HashMap<String, String>();
            }
            pagefotters.put(tableName, pagefotter);
            return this;
        }

        public Builder addRangeAddresses(String sheetName, CellRangeAddress... rangeAddress)
        {
            if (CoreUtil.isEmpty(rangeAddress))
            {
                return this;
            }
            if (rangeAddresses == null)
            {
                rangeAddresses = new HashMap<String, List<CellRangeAddress>>();
            }
            List<CellRangeAddress> addresses = null;
            if ((addresses = rangeAddresses.get(sheetName)) == null)
            {
                addresses = new ArrayList<CellRangeAddress>();
                rangeAddresses.put(sheetName, addresses);
            }
            for (CellRangeAddress cellRangeAddress : rangeAddress)
            {
                addresses.add(cellRangeAddress);
            }
            return this;
        }

        public Builder addRangeAddresses(String sheetName, IdefinedRangeAddress... rangeAddress)
        {
            if (CoreUtil.isEmpty(rangeAddress))
            {
                return this;
            }
            if (rangeAddresses == null)
            {
                rangeAddresses = new HashMap<String, List<CellRangeAddress>>();
            }
            List<CellRangeAddress> addresses = null;
            if ((addresses = rangeAddresses.get(sheetName)) == null)
            {
                addresses = new ArrayList<CellRangeAddress>();
                rangeAddresses.put(sheetName, addresses);
            }
            for (IdefinedRangeAddress cellRangeAddress : rangeAddress)
            {
                addresses.add(cellRangeAddress.toCellRangeAddress());
            }
            return this;
        }

        public Builder addSplitFlag(String tableName, String splitFlag)
        {
            if (splitFlags == null)
            {
                splitFlags = new HashMap<String, String>();
            }
            splitFlags.put(tableName, splitFlag);
            return this;
        }

        public Builder addTitle(String tableName, String title)
        {
            if (titles == null)
            {
                titles = new HashMap<String, String>();
            }
            titles.put(tableName, title);
            return this;
        }

        public DataSet2Excel build()
        {
            return new DataSet2Excel(this);
        }

        public Builder setBookConfig(WorkBookConfig bookConfig)
        {
            this.bookConfig = bookConfig;
            return this;
        }

        public Builder setColumnDataType(String tableName, String columnName, String dataType)
        {
            if (dataTypeMap == null)
            {
                dataTypeMap = new HashMap<String, Map<String, String>>();
            }
            Map<String, String> table = dataTypeMap.get(tableName);
            if (table == null)
            {
                table = new HashMap<String, String>();
                dataTypeMap.put(tableName, table);
            }
            table.put(columnName, dataType);
            return this;
        }

        public Builder setCombineMap(Map<String, Set<? extends CombineColumnConfig>> combineMap)
        {
            if (this.combineMap == null)
            {
                this.combineMap = new HashMap<String, Set<? extends CombineColumnConfig>>();
            }
            this.combineMap.putAll(combineMap);
            return this;
        }

        public Builder setCreateHeader(boolean createHeader)
        {
            this.createHeader = createHeader;
            return this;
        }

        public Builder setCreateTitle(boolean createTitle)
        {
            this.createTitle = createTitle;
            return this;
        }

        public Builder setDataSet(DataTables ds)
        {
            if (this.ds == null)
            {
                this.ds = new DataTables();
            }
            this.ds.addAll(ds);
            return this;
        }

        public Builder setExcelType(String excelType)
        {
            this.excelType = excelType;
            return this;
        }

        public Builder setFreezeColIndex(int freezeColIndex)
        {
            this.freezeColIndex = freezeColIndex;
            return this;
        }

        public Builder setFreezeHeader(boolean freezeHeader)
        {
            this.freezeHeader = freezeHeader;
            return this;
        }

        public Builder setPagefotters(Map<String, String> pagefotters)
        {
            if (this.pagefotters == null)
            {
                this.pagefotters = new HashMap<String, String>();
            }
            this.pagefotters.putAll(pagefotters);
            return this;
        }

        public Builder setRangeAddresses(Map<String, List<CellRangeAddress>> rangeAddresses)
        {
            this.rangeAddresses = rangeAddresses;
            return this;
        }

        public Builder setRecalWidth(boolean recalWidth)
        {
            this.recalWidth = recalWidth;
            return this;
        }

        public Builder setSameNameCache(Map<String, String> sameNameCache)
        {
            this.sameNameCache = sameNameCache;
            return this;
        }

        public Builder setSplitFlag(String splitFlag)
        {
            this.splitFlag = splitFlag;
            return this;
        }

        public Builder setSplitFlags(Map<String, String> splitFlags)
        {
            this.splitFlags = splitFlags;
            return this;
        }

        public Builder setTitles(Map<String, String> titles)
        {
            this.titles = titles;
            return this;
        }

        public Builder setWorkbook(Workbook workbook)
        {
            this.workbook = workbook;
            return this;
        }
    }
}
